#include <glad/glad.h>

#define NK_INCLUDE_FIXED_TYPES
#define NK_INCLUDE_STANDARD_IO
#define NK_INCLUDE_STANDARD_VARARGS
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#define NK_INCLUDE_DEFAULT_FONT
#define NK_IMPLEMENTATION
#include <nuklear.hpp>

#define NK_SDL_GL3_IMPLEMENTATION
#define sdl _sdl_
#include "nuklear_sdl_gl3.h"
#undef sdl

#include "inc.h"

#include <iostream>

#define MAX_VERTEX_MEMORY 512 * 1024
#define MAX_ELEMENT_MEMORY 128 * 1024

sdl::Window::Wrapped window_;
gl::Context context_ = nullptr;
nk_context *nkctx_ = nullptr;

void handleEvent(sdl::event::Event &evt, IDrawable *drawable) {
  if (evt.type == SDL_KEYDOWN) {
    if (evt.key.keysym.sym == SDLK_ESCAPE) {
      sdl::event::Event close;
      close.type = SDL_QUIT;
      sdl::event::push(&close);
    }
  } else if (evt.type == SDL_WINDOWEVENT) {
    if (evt.window.event == SDL_WINDOWEVENT_RESIZED)
      glViewport(0, 0, evt.window.data1, evt.window.data2);
  }

  drawable->processInput(evt);
}

void render(IDrawable *drawable) {
  int width, height;
  window_->getSize(&width, &height);
  struct nk_rect rc = { 0.f, 0.f, (float)width, (float)height };
  auto bkcolor = drawable->bkcolor();

  glClearColor(bkcolor.r, bkcolor.g, bkcolor.b, bkcolor.a);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  drawable->render();
  drawable->imgui(nkctx_, rc);

  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  nk_sdl_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_MEMORY, MAX_ELEMENT_MEMORY);

  gl::swapWindow(window_);
}


int main(int argc, char *argv[]) {
  IDrawable *drawable = nullptr;
  struct nk_font_atlas *atlas = nullptr;

  gl::setAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  gl::setAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
  gl::setAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

  auto rst = sdl::init(SDL_INIT_EVERYTHING);
  if (sdl::isFailed(rst)) {
    std::cout << "error init." << std::endl;
    return -1;
  }

  drawable = createDrawable();
  if (!drawable) {
    std::cout << "error create drawable." << std::endl;
    goto error;
  }

  window_ = sdl::Window::create(drawable->title()
      , SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED
      , 640, 480
      , SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
  if (!window_) {
    std::cout << "error create window." << std::endl;
    goto error;
  }

  context_ = gl::createContext(window_);
  if (!context_) {
    std::cout << "error create gl context." << std::endl;
    goto error;
  }

  if (!gladLoadGLLoader((GLADloadproc)gl::getProcAddress)) {
    std::cout << "error gladLoadGLLoader." << std::endl;
    goto error;
  }

  nkctx_ = nk_sdl_init(window_);
  if (!nkctx_) {
    std::cout << "error create nuklear context." << std::endl;
    goto error;
  } else {
    nk_sdl_font_stash_begin(&atlas);
    nk_sdl_font_stash_end();
  }

  drawable->init();

  bool quit = false;
  sdl::event::Event evt;
  Uint32 lastTime = 0, curTime = 0;
  float elapsedTime = 0.f;
  while (!quit) {
    nk_input_begin(nkctx_);
    while (sdl::event::poll(&evt)) {
      if (evt.type == SDL_QUIT) {
        quit = true;
      }
      nk_sdl_handle_event(&evt);
      handleEvent(evt, drawable);
    }
    nk_input_end(nkctx_);

    curTime = sdl::timer::getTicks();
    elapsedTime = (curTime - lastTime) / 1000.f;
    lastTime = curTime;
    drawable->update(elapsedTime);

    render(drawable);
  }

  drawable->uninit();

 error:
  if (nkctx_) nk_sdl_shutdown();
  if (context_) gl::deleteContext(context_);
  if (window_) window_->destroy();
  if (drawable) drawable->release();
  sdl::quit();
  return 0;
}